Wire CLIProxy-backed source-grounded generation#9
Conversation
랜딩 추가 컴포넌트 + 출제 워크플로우 전 화면 구현. 랜딩: - components/landing/footer.tsx: 3-col (소개·도움말·피드백) + fine-print - components/landing/faq.tsx: 4 native disclosure-row - components/landing/book-stage.tsx: iPad showcase 5-step walkthrough (S0→S1→S2→S3→S4 자동 순환, BrandMark 사용) - components/landing/nav.tsx: 3-grid 정중앙 + 로그인 버튼 - hero CTA: /app, /samples 로 연결 출제 워크플로우 (S0~S6, productivity surface): - /app: S0 워크스페이스 (hero-tile + 2 entry cards) - /app/new/grade: S1 학년 선택 (3 intent-radio-card) - /app/new/topic: S2 단원 선택 (filter-chip + 30 단원 정적 데이터) - /app/new/intent: S3 동형 모드 + 평가 차원 (radio + checkbox) - /app/new/verify: S4 검증 6 단계 (SSE 실시간) - /app/new/result: S5 결과 + 채택 (mock.ts, agent 미연동) - /app/new/export: S6 PDF 출력 (브라우저 print API) 부가 페이지: - /login: 로그인 폼 (mock auth — 제출 시 /app 라우팅) - /samples: 검증 통과 예시 3 문항 공통 인프라: - hooks/use-verification-stream.ts: @microsoft/fetch-event-source 기반 SSE 컨슈머 + AbortController + React 18 strict-mode 가드 - components/math/latex-renderer.tsx: KaTeX SSR (block + inline + $..$ / $$..$$ 자동 분할 LatexMixed) - components/app/primary-nav.tsx: productivity nav DESIGN.md 준수: - btn-primary drop shadow 제거 (brand-mark glow 재현 금지 규칙) - 한 fold 1 primary: nav 버튼 → btn-ghost / btn-secondary - LaTeX 항상 formula-stage (soft-cloud) 위 — S5 풀이 inline 전환 - inline hex 0건, as any/@ts-ignore 0건 - prefers-reduced-motion: spinner-dot, intent cards, step rows 등 커버 기타: - .gitignore: docs/product/ 화이트리스트 (USER_FLOW, SCREENS, MOCKUPS) - README: 작업 진행 상태 + 라우트 맵 + 신규 deps + mock 한계 명시 - globals.css: orphan CSS ~1000줄 정리 (옛 book v1/v2, tablet v1, mock-intro/result, token-v2, tablet-signal/brand-mark/login-btn) - 새 deps: @microsoft/fetch-event-source 2.0.1, katex 0.16.x
P1: stabilize SSE hook deps via ref snapshot; render verify preview LaTeX with KaTeX renderer instead of raw text. P2: reset stream state on reconnect (drop stale steps/preview/error); neutralize print-dialog notice (afterprint cannot tell save vs cancel); disable v2-only result actions with sr-only describedby; drop role="img"/aria-label on formula stages so KaTeX MathML reaches screen readers. P3: SCREENS typo brower-native -> browser-native; simplify export defaultTitle via useState lazy init; drop stepIdx from book-stage interval deps (functional updater is sufficient).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
There was a problem hiding this comment.
11 issues found across 68 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/web/app/app/new/verify/page.tsx">
<violation number="1" location="packages/web/app/app/new/verify/page.tsx:20">
P3: This page duplicates query parsing logic (`parseMode`/`parseDims`) that already exists in nearby pages; extract and reuse a shared parser to prevent divergence.</violation>
</file>
<file name="packages/agent/src/index.ts">
<violation number="1" location="packages/agent/src/index.ts:45">
P2: Hard-coded `allowedHosts` blocks all non-local LLM base URLs, breaking valid `openai-compatible` / `anthropic-via-compatible` configurations.</violation>
</file>
<file name="packages/agent/src/steps/sympy-verification.ts">
<violation number="1" location="packages/agent/src/steps/sympy-verification.ts:59">
P1: Hard-failing when `question_text` lacks `=` makes all non-equation candidates automatically rejected by policy.</violation>
<violation number="2" location="packages/agent/src/steps/sympy-verification.ts:80">
P2: Use a word-bounded, case-insensitive `or` delimiter to avoid malformed answer tokenization.</violation>
</file>
<file name="packages/agent/src/tools/schema-loader.ts">
<violation number="1" location="packages/agent/src/tools/schema-loader.ts:46">
P2: `load(code)` does not verify that the YAML's `strategy.code` matches the requested code, which can return/cache the wrong strategy for that lookup.</violation>
</file>
<file name="packages/agent/src/tools/llm-provider.ts">
<violation number="1" location="packages/agent/src/tools/llm-provider.ts:50">
P2: This adds duplicate base-URL validation logic that already exists in `math-engine-client.ts`; centralize it in a shared utility to avoid security-rule drift.</violation>
</file>
<file name="packages/agent/src/schemas/generate-request.schema.ts">
<violation number="1" location="packages/agent/src/schemas/generate-request.schema.ts:43">
P1: The new topic requirement treats empty strings as valid, so invalid requests can pass schema validation and fail later in retrieval.</violation>
<violation number="2" location="packages/agent/src/schemas/generate-request.schema.ts:55">
P2: `getGenerateRequestTopicCode` can return an empty `topic_code` and ignore a valid `topic`, which breaks fallback semantics.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| source_problem_text: z.string().optional(), | ||
| }).superRefine((request, ctx) => { | ||
| if (request.topic === undefined && request.topic_code === undefined) { |
There was a problem hiding this comment.
P1: The new topic requirement treats empty strings as valid, so invalid requests can pass schema validation and fail later in retrieval.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/schemas/generate-request.schema.ts, line 43:
<comment>The new topic requirement treats empty strings as valid, so invalid requests can pass schema validation and fail later in retrieval.</comment>
<file context>
@@ -25,14 +25,32 @@ export const GenerateRequestSchema = z.object({
source_problem_text: z.string().optional(),
+}).superRefine((request, ctx) => {
+ if (request.topic === undefined && request.topic_code === undefined) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
</file context>
| if (request.topic === undefined && request.topic_code === undefined) { | |
| if ((request.topic?.trim() ?? "") === "" && (request.topic_code?.trim() ?? "") === "") { |
| mathEngine: MathEngineClient, | ||
| candidate: GeneratedProblem, | ||
| ): Promise<boolean> { | ||
| if (!candidate.question_text.includes("=")) { |
There was a problem hiding this comment.
P1: Hard-failing when question_text lacks = makes all non-equation candidates automatically rejected by policy.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/steps/sympy-verification.ts, line 59:
<comment>Hard-failing when `question_text` lacks `=` makes all non-equation candidates automatically rejected by policy.</comment>
<file context>
@@ -16,8 +16,88 @@ export interface SympyVerificationOutput {
+ mathEngine: MathEngineClient,
+ candidate: GeneratedProblem,
+): Promise<boolean> {
+ if (!candidate.question_text.includes("=")) {
+ throw new Error("SymPy verification supports equation candidates only");
+ }
</file context>
| } | ||
| const strategy = await readStrategyFile(join(opts.strategiesDir, `${code}.yaml`)); | ||
| if (strategy === null) return null; | ||
| cache.set(strategy.code, strategy); |
There was a problem hiding this comment.
P2: load(code) does not verify that the YAML's strategy.code matches the requested code, which can return/cache the wrong strategy for that lookup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/tools/schema-loader.ts, line 46:
<comment>`load(code)` does not verify that the YAML's `strategy.code` matches the requested code, which can return/cache the wrong strategy for that lookup.</comment>
<file context>
@@ -22,7 +31,64 @@ export interface FsStrategyLoaderOptions {
+ }
+ const strategy = await readStrategyFile(join(opts.strategiesDir, `${code}.yaml`));
+ if (strategy === null) return null;
+ cache.set(strategy.code, strategy);
+ return strategy;
+ }
</file context>
| cache.set(strategy.code, strategy); | |
| if (strategy.code !== code) { | |
| throw new Error(`Strategy code mismatch in ${code}.yaml: expected ${code}, got ${strategy.code}`); | |
| } | |
| cache.set(code, strategy); |
| export type GenerateRequest = z.infer<typeof GenerateRequestSchema>; | ||
|
|
||
| export function getGenerateRequestTopicCode(request: GenerateRequest): string { | ||
| return request.topic_code ?? request.topic ?? ""; |
There was a problem hiding this comment.
P2: getGenerateRequestTopicCode can return an empty topic_code and ignore a valid topic, which breaks fallback semantics.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/schemas/generate-request.schema.ts, line 55:
<comment>`getGenerateRequestTopicCode` can return an empty `topic_code` and ignore a valid `topic`, which breaks fallback semantics.</comment>
<file context>
@@ -25,14 +25,32 @@ export const GenerateRequestSchema = z.object({
export type GenerateRequest = z.infer<typeof GenerateRequestSchema>;
+
+export function getGenerateRequestTopicCode(request: GenerateRequest): string {
+ return request.topic_code ?? request.topic ?? "";
+}
</file context>
| return request.topic_code ?? request.topic ?? ""; | |
| return request.topic_code?.trim() || request.topic?.trim() || ""; |
| modelId: llmModel, | ||
| baseUrl: llmBaseUrl, | ||
| apiKey: llmApiKey ?? "openmath-local", | ||
| allowedHosts: ["localhost", "127.0.0.1"], |
There was a problem hiding this comment.
P2: Hard-coded allowedHosts blocks all non-local LLM base URLs, breaking valid openai-compatible / anthropic-via-compatible configurations.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/index.ts, line 45:
<comment>Hard-coded `allowedHosts` blocks all non-local LLM base URLs, breaking valid `openai-compatible` / `anthropic-via-compatible` configurations.</comment>
<file context>
@@ -1,14 +1,70 @@
+ modelId: llmModel,
+ baseUrl: llmBaseUrl,
+ apiKey: llmApiKey ?? "openmath-local",
+ allowedHosts: ["localhost", "127.0.0.1"],
+ }),
+ modelId: llmModel,
</file context>
| allowedHosts: ["localhost", "127.0.0.1"], | |
| allowedHosts: env.LLM_PROVIDER === "cliproxy" ? ["localhost", "127.0.0.1"] : undefined, |
|
|
||
| function parseExpectedSolutions(answer: string): string[] { | ||
| return answer | ||
| .split(/[,;]|또는|or/) |
There was a problem hiding this comment.
P2: Use a word-bounded, case-insensitive or delimiter to avoid malformed answer tokenization.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/steps/sympy-verification.ts, line 80:
<comment>Use a word-bounded, case-insensitive `or` delimiter to avoid malformed answer tokenization.</comment>
<file context>
@@ -16,8 +16,88 @@ export interface SympyVerificationOutput {
+
+function parseExpectedSolutions(answer: string): string[] {
+ return answer
+ .split(/[,;]|또는|or/)
+ .map((part) => part.trim())
+ .map((part) => part.replace(/^[a-zA-Z]\s*=\s*/, ""))
</file context>
| .split(/[,;]|또는|or/) | |
| .split(/[,;]|또는|\bor\b/i) |
| }; | ||
| }; | ||
|
|
||
| function parseMode(raw: string | null): "structural" | "conceptual" | null { |
There was a problem hiding this comment.
P3: This page duplicates query parsing logic (parseMode/parseDims) that already exists in nearby pages; extract and reuse a shared parser to prevent divergence.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/app/app/new/verify/page.tsx, line 20:
<comment>This page duplicates query parsing logic (`parseMode`/`parseDims`) that already exists in nearby pages; extract and reuse a shared parser to prevent divergence.</comment>
<file context>
@@ -0,0 +1,49 @@
+ };
+};
+
+function parseMode(raw: string | null): "structural" | "conceptual" | null {
+ if (raw === "structural" || raw === "conceptual") return raw;
+ return null;
</file context>
Resolve 15 conflicts after PR #7 (web S0-S6, squashed) and PR #8 (RAG canonical JSONL client) landed on main. - packages/agent/src/tools/rag-client.ts: take main's canonical OpenMathRagRecord-based client (D-7) and integrate source_problem_text matching from this branch (source-grounded generation). - 14 web files: take ours (this branch). PR #7 was squash-merged so its files appeared as add/add; this branch already contains PR #7's contents plus subsequent commits (source-grounded workflow, demo navigation polish, BrandMark removal in favor of plain wordmark). Verified: no commits touched packages/web/ on main after PR #7 merge.
There was a problem hiding this comment.
11 issues found across 30 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/web/app/globals.css">
<violation number="1" location="packages/web/app/globals.css:2213">
P2: Keyboard-focus branch is unreachable because `.book-cover` elements are not focusable, so `:focus-visible` never applies.</violation>
</file>
<file name="packages/agent/src/tools/latex-formatter.ts">
<violation number="1" location="packages/agent/src/tools/latex-formatter.ts:29">
P1: Global `*` replacement corrupts unresolved power operators (`**`) into whitespace, producing mathematically incorrect LaTeX output.</violation>
</file>
<file name="packages/agent/src/agents/refiner-agent.ts">
<violation number="1" location="packages/agent/src/agents/refiner-agent.ts:39">
P2: RefinerAgent ignores its configured refiner prompt/model and always reuses GeneratorAgent, making refiner-specific wiring dead and potentially bypassing intended refinement behavior.</violation>
</file>
<file name="packages/agent/src/steps/problem-generation.ts">
<violation number="1" location="packages/agent/src/steps/problem-generation.ts:109">
P1: The failure path never returns `gate: failed` because `await Promise.reject(err)` throws during object construction.</violation>
<violation number="2" location="packages/agent/src/steps/problem-generation.ts:137">
P2: Calling `mathEngine.solve` on raw `question_text` can hard-fail generation for non-parseable text that includes `=`.</violation>
</file>
<file name="packages/web/components/landing/book-stage.tsx">
<violation number="1" location="packages/web/components/landing/book-stage.tsx:134">
P2: Parallax mouse tracking is bound to `window`, so off-stage mouse movement keeps driving stage transforms and breaks the intended stage-local behavior.</violation>
</file>
<file name="packages/agent/src/steps/objective-mapping.ts">
<violation number="1" location="packages/agent/src/steps/objective-mapping.ts:161">
P3: Duplicate `normalizeMathText` logic was added instead of reusing a shared utility, creating a drift-prone normalization path.</violation>
</file>
<file name="packages/agent/.env.example">
<violation number="1" location="packages/agent/.env.example:28">
P3: Canonical alias priority is described backwards. The comment says to use `LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL` "when none of the above 3 options map" (fallback), but in `src/index.ts` they are the **highest-priority** values checked first via `??`. If a user follows 옵션 2 (OpenAI direct) by uncommenting `OPENAI_API_KEY`/`OPENAI_MODEL` and switching `LLM_PROVIDER=openai` without also disabling the canonical aliases, `LLM_BASE_URL=http://localhost:8317/v1` and `LLM_API_KEY=dummy-key` would silently override the OpenAI settings — routing to CLIProxy with dummy key instead of OpenAI.</violation>
</file>
<file name="packages/agent/src/steps/independent-resolve.ts">
<violation number="1" location="packages/agent/src/steps/independent-resolve.ts:92">
P2: `parseAnswers` splits on `or` as a plain substring, which can corrupt valid expressions (e.g. `floor(x)`) and create false re-solve mismatches.</violation>
</file>
<file name="packages/agent/prompts/problem-generator.md">
<violation number="1" location="packages/agent/prompts/problem-generator.md:60">
P0: Prompt instructs LLM to output `question_text` and `expected_answer` in KaTeX LaTeX format, but the downstream SymPy verification pipeline expects standard SymPy expressions. The math engine (`packages/math-engine/src/routers/math.py`) uses `sympy.parsing.sympy_parser.parse_expr` with `standard_transformations + implicit_multiplication_application + convert_xor` — this can parse `x**2`, `sqrt(7)`, `2*x + 3` but CANNOT parse LaTeX syntax like `x^{2}`, `\sqrt{7}`, `5x`. No LaTeX-to-SymPy conversion exists in the pipeline. The `formatLatex` function (`packages/agent/src/tools/latex-formatter.ts`) converts from SymPy → KaTeX, not the reverse. Result: when the LLM follows the prompt and outputs LaTeX, both the generation step's `normalizeExpectedAnswer` (in `problem-generation.ts:137`) and the verification step's `verifyWithSympy` (in `sympy-verification.ts:68,73`) will send unparseable expressions to the math engine, causing HTTP 400 parse errors and cascading step failures.</violation>
</file>
<file name="packages/agent/src/workflows/verification-workflow.ts">
<violation number="1" location="packages/agent/src/workflows/verification-workflow.ts:66">
P2: Top-level dependency assertion can throw before first yield, causing `/api/generate` SSE to terminate without a structured error event.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| - `proposed_solution_trace`에 풀이 단계 명시 | ||
| - `inferred_intent.objective_code == intent.objective_code` (I-G1) | ||
| JSON으로만 응답. | ||
| - `question_text`는 KaTeX가 바로 렌더링할 수 있는 LaTeX `x` 방정식 문자열이어야 한다. `**`, `*`를 쓰지 말고 지수는 `x^{2}`, 곱셈은 `5x`, 제곱근은 `\\sqrt{7}`처럼 쓴다. |
There was a problem hiding this comment.
P0: Prompt instructs LLM to output question_text and expected_answer in KaTeX LaTeX format, but the downstream SymPy verification pipeline expects standard SymPy expressions. The math engine (packages/math-engine/src/routers/math.py) uses sympy.parsing.sympy_parser.parse_expr with standard_transformations + implicit_multiplication_application + convert_xor — this can parse x**2, sqrt(7), 2*x + 3 but CANNOT parse LaTeX syntax like x^{2}, \sqrt{7}, 5x. No LaTeX-to-SymPy conversion exists in the pipeline. The formatLatex function (packages/agent/src/tools/latex-formatter.ts) converts from SymPy → KaTeX, not the reverse. Result: when the LLM follows the prompt and outputs LaTeX, both the generation step's normalizeExpectedAnswer (in problem-generation.ts:137) and the verification step's verifyWithSympy (in sympy-verification.ts:68,73) will send unparseable expressions to the math engine, causing HTTP 400 parse errors and cascading step failures.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/prompts/problem-generator.md, line 60:
<comment>Prompt instructs LLM to output `question_text` and `expected_answer` in KaTeX LaTeX format, but the downstream SymPy verification pipeline expects standard SymPy expressions. The math engine (`packages/math-engine/src/routers/math.py`) uses `sympy.parsing.sympy_parser.parse_expr` with `standard_transformations + implicit_multiplication_application + convert_xor` — this can parse `x**2`, `sqrt(7)`, `2*x + 3` but CANNOT parse LaTeX syntax like `x^{2}`, `\sqrt{7}`, `5x`. No LaTeX-to-SymPy conversion exists in the pipeline. The `formatLatex` function (`packages/agent/src/tools/latex-formatter.ts`) converts from SymPy → KaTeX, not the reverse. Result: when the LLM follows the prompt and outputs LaTeX, both the generation step's `normalizeExpectedAnswer` (in `problem-generation.ts:137`) and the verification step's `verifyWithSympy` (in `sympy-verification.ts:68,73`) will send unparseable expressions to the math engine, causing HTTP 400 parse errors and cascading step failures.</comment>
<file context>
@@ -57,8 +57,10 @@ updated: 2026-05-18
JSON으로만 응답.
-- `question_text`는 SymPy가 풀 수 있는 `x` 방정식 문자열이어야 한다. 예: `x**2 - 7 = 0`, `2*x + 3 = 11`.
-- `expected_answer`는 `sqrt(7), -sqrt(7)`처럼 쉼표로 구분한다. 서버가 SymPy로 다시 계산해 최종 정답을 검증한다.
+- `question_text`는 KaTeX가 바로 렌더링할 수 있는 LaTeX `x` 방정식 문자열이어야 한다. `**`, `*`를 쓰지 말고 지수는 `x^{2}`, 곱셈은 `5x`, 제곱근은 `\\sqrt{7}`처럼 쓴다.
+- `expected_answer`도 LaTeX로 쓴다. 예: `2, 5`, `\\sqrt{7}, -\\sqrt{7}`.
+- 예시 출력: `{ "question_text": "x^{2} - 7x + 10 = 0", "expected_answer": "2, 5", "proposed_solution_trace": "인수분해하면 (x - 2)(x - 5) = 0이므로 x = 2 또는 x = 5이다." }`
</file context>
| } | ||
|
|
||
| function formatMultiplication(value: string): string { | ||
| return value.replace(/\s*\*\s*/g, " "); |
There was a problem hiding this comment.
P1: Global * replacement corrupts unresolved power operators (**) into whitespace, producing mathematically incorrect LaTeX output.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/tools/latex-formatter.ts, line 29:
<comment>Global `*` replacement corrupts unresolved power operators (`**`) into whitespace, producing mathematically incorrect LaTeX output.</comment>
<file context>
@@ -0,0 +1,87 @@
+}
+
+function formatMultiplication(value: string): string {
+ return value.replace(/\s*\*\s*/g, " ");
+}
+
</file context>
| }; | ||
| } catch (err) { | ||
| return { | ||
| data: await Promise.reject(err), |
There was a problem hiding this comment.
P1: The failure path never returns gate: failed because await Promise.reject(err) throws during object construction.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/steps/problem-generation.ts, line 109:
<comment>The failure path never returns `gate: failed` because `await Promise.reject(err)` throws during object construction.</comment>
<file context>
@@ -27,16 +33,111 @@ export interface ProblemGenerationInput {
+ };
+ } catch (err) {
+ return {
+ data: await Promise.reject(err),
+ gate: {
+ step: "generate",
</file context>
| display: flex; | ||
| gap: 8px; | ||
| .book-cover:hover .book-cover-front, | ||
| .book-cover:focus-visible .book-cover-front { |
There was a problem hiding this comment.
P2: Keyboard-focus branch is unreachable because .book-cover elements are not focusable, so :focus-visible never applies.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/app/globals.css, line 2213:
<comment>Keyboard-focus branch is unreachable because `.book-cover` elements are not focusable, so `:focus-visible` never applies.</comment>
<file context>
@@ -2059,722 +2059,426 @@
- display: flex;
- gap: 8px;
+ .book-cover:hover .book-cover-front,
+ .book-cover:focus-visible .book-cover-front {
+ transform: rotateY(-168deg);
}
</file context>
| ...input.hints.map((hint) => `- ${hint}`), | ||
| ].join("\n"); | ||
|
|
||
| return deps.generator.generate({ |
There was a problem hiding this comment.
P2: RefinerAgent ignores its configured refiner prompt/model and always reuses GeneratorAgent, making refiner-specific wiring dead and potentially bypassing intended refinement behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/agents/refiner-agent.ts, line 39:
<comment>RefinerAgent ignores its configured refiner prompt/model and always reuses GeneratorAgent, making refiner-specific wiring dead and potentially bypassing intended refinement behavior.</comment>
<file context>
@@ -16,9 +21,29 @@ export interface RefinerAgent {
+ ...input.hints.map((hint) => `- ${hint}`),
+ ].join("\n");
+
+ return deps.generator.generate({
+ request: input.request,
+ intent: input.intent,
</file context>
| window.addEventListener("mousemove", handleMove, { passive: true }); | ||
| node.addEventListener("mouseleave", handleLeave); | ||
| return () => { | ||
| window.removeEventListener("mousemove", handleMove); | ||
| node.removeEventListener("mouseleave", handleLeave); |
There was a problem hiding this comment.
P2: Parallax mouse tracking is bound to window, so off-stage mouse movement keeps driving stage transforms and breaks the intended stage-local behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/components/landing/book-stage.tsx, line 134:
<comment>Parallax mouse tracking is bound to `window`, so off-stage mouse movement keeps driving stage transforms and breaks the intended stage-local behavior.</comment>
<file context>
@@ -69,287 +112,98 @@ export function BookStage() {
+ node.style.setProperty("--mouse-x", "0");
+ node.style.setProperty("--mouse-y", "0");
+ };
+ window.addEventListener("mousemove", handleMove, { passive: true });
+ node.addEventListener("mouseleave", handleLeave);
+ return () => {
</file context>
| window.addEventListener("mousemove", handleMove, { passive: true }); | |
| node.addEventListener("mouseleave", handleLeave); | |
| return () => { | |
| window.removeEventListener("mousemove", handleMove); | |
| node.removeEventListener("mouseleave", handleLeave); | |
| node.addEventListener("mousemove", handleMove, { passive: true }); | |
| node.addEventListener("mouseleave", handleLeave); | |
| return () => { | |
| node.removeEventListener("mousemove", handleMove); | |
| node.removeEventListener("mouseleave", handleLeave); | |
| }; |
|
|
||
| function parseAnswers(answer: string): string[] { | ||
| return answer | ||
| .split(/[,;]|또는|or/) |
There was a problem hiding this comment.
P2: parseAnswers splits on or as a plain substring, which can corrupt valid expressions (e.g. floor(x)) and create false re-solve mismatches.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/steps/independent-resolve.ts, line 92:
<comment>`parseAnswers` splits on `or` as a plain substring, which can corrupt valid expressions (e.g. `floor(x)`) and create false re-solve mismatches.</comment>
<file context>
@@ -16,12 +18,89 @@ export interface IndependentResolveInput {
+
+function parseAnswers(answer: string): string[] {
+ return answer
+ .split(/[,;]|또는|or/)
+ .map((part) => part.trim())
+ .map((part) => part.replace(/^[a-zA-Z]\s*=\s*/, ""))
</file context>
| .split(/[,;]|또는|or/) | |
| .split(/[,;]|또는|\bor\b/i) |
| options?: RunOptions, | ||
| ): AsyncGenerator<WorkflowYield, WorkflowReturn, void> { | ||
| throw new Error("runVerificationWorkflow: not implemented yet"); | ||
| assertRequiredDeps(deps); |
There was a problem hiding this comment.
P2: Top-level dependency assertion can throw before first yield, causing /api/generate SSE to terminate without a structured error event.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/workflows/verification-workflow.ts, line 66:
<comment>Top-level dependency assertion can throw before first yield, causing `/api/generate` SSE to terminate without a structured error event.</comment>
<file context>
@@ -63,15 +61,20 @@ export type WorkflowReturn = {
- _options?: RunOptions,
+ options?: RunOptions,
): AsyncGenerator<WorkflowYield, WorkflowReturn, void> {
+ assertRequiredDeps(deps);
+
const timestamp = () => new Date().toISOString();
</file context>
| assertRequiredDeps(deps); | |
| try { | |
| assertRequiredDeps(deps); | |
| } catch (err) { | |
| yield { | |
| type: "error", | |
| stage: "orchestrator", | |
| code: "workflow_dependencies_missing", | |
| message: err instanceof Error ? err.message : String(err), | |
| recoverable: false, | |
| timestamp: new Date().toISOString(), | |
| }; | |
| return { verifications: [] }; | |
| } |
| return normalizeMathText(left) === normalizeMathText(right); | ||
| } | ||
|
|
||
| function normalizeMathText(value: string): string { |
There was a problem hiding this comment.
P3: Duplicate normalizeMathText logic was added instead of reusing a shared utility, creating a drift-prone normalization path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/src/steps/objective-mapping.ts, line 161:
<comment>Duplicate `normalizeMathText` logic was added instead of reusing a shared utility, creating a drift-prone normalization path.</comment>
<file context>
@@ -2,31 +2,165 @@
+ return normalizeMathText(left) === normalizeMathText(right);
+}
+
+function normalizeMathText(value: string): string {
+ return value
+ .replace(/²/g, "**2")
</file context>
| # OPENAI_API_KEY=sk-... | ||
| # OPENAI_MODEL=gpt-4o | ||
|
|
||
| # Canonical aliases — 위 3가지 옵션 중 어느 것도 매핑되지 않으면 이 3개로 직접 지정 가능 |
There was a problem hiding this comment.
P3: Canonical alias priority is described backwards. The comment says to use LLM_BASE_URL/LLM_API_KEY/LLM_MODEL "when none of the above 3 options map" (fallback), but in src/index.ts they are the highest-priority values checked first via ??. If a user follows 옵션 2 (OpenAI direct) by uncommenting OPENAI_API_KEY/OPENAI_MODEL and switching LLM_PROVIDER=openai without also disabling the canonical aliases, LLM_BASE_URL=http://localhost:8317/v1 and LLM_API_KEY=dummy-key would silently override the OpenAI settings — routing to CLIProxy with dummy key instead of OpenAI.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/.env.example, line 28:
<comment>Canonical alias priority is described backwards. The comment says to use `LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL` "when none of the above 3 options map" (fallback), but in `src/index.ts` they are the **highest-priority** values checked first via `??`. If a user follows 옵션 2 (OpenAI direct) by uncommenting `OPENAI_API_KEY`/`OPENAI_MODEL` and switching `LLM_PROVIDER=openai` without also disabling the canonical aliases, `LLM_BASE_URL=http://localhost:8317/v1` and `LLM_API_KEY=dummy-key` would silently override the OpenAI settings — routing to CLIProxy with dummy key instead of OpenAI.</comment>
<file context>
@@ -1,20 +1,31 @@
+# OPENAI_API_KEY=sk-...
+# OPENAI_MODEL=gpt-4o
+
+# Canonical aliases — 위 3가지 옵션 중 어느 것도 매핑되지 않으면 이 3개로 직접 지정 가능
LLM_BASE_URL=http://localhost:8317/v1
LLM_API_KEY=dummy-key
</file context>
| # Canonical aliases — 위 3가지 옵션 중 어느 것도 매핑되지 않으면 이 3개로 직접 지정 가능 | |
| # Canonical aliases — CLIProxy/OpenAI 를 통틀어 최우선 적용. 아래 LLM_BASE_URL/LLM_API_KEY/LLM_MODEL 이 설정되면 CLIPROXY_*, OPENAI_* 보다 먼저 사용됨. (옵션 2 로 전환 시 다른 값과 충돌하지 않도록 비활성화) |
Summary
/api/generate, preserving generated results through result/export.Verification
Notes
.envor OAuth credential files are included..env.examplecontains placeholders only..playwright-mcp/,AI_HUB_data/, and extra untracked product previews were left uncommitted.Summary by cubic
Wired source-grounded generation end to end with a CLIProxy/OpenAI‑compatible LLM and SSE streaming; shipped the full S0–S6 product flow using the canonical JSONL RAG client. Implemented 6-step verification with specialist agents, per-step policies, and exposed rejected results with a visual guard (I‑G4).
New Features
/api/generatestreams SSE in the FE wire format with a new wire adapter; CORS enabled forhttp://localhost:3001.cliproxy,openai, andopenai-compatible; prompt/strategy FS loaders; LaTeX formatter; math-engine HTTP client.problem-generatorprompt updated (lower temp, explicit source context).sourcethrough verify/result/export; SSE hook acceptssourceProblemText(@microsoft/fetch-event-source); result filters drop “실패” (policy shows failed as cards); KaTeX rendering; nav/landing refresh; favicon.Migration
@openmath/agent, copy.env.example. SetLLM_PROVIDERand model:cliproxy:LLM_PROVIDER=cliproxy,LLM_BASE_URL=http://localhost:8317/v1,LLM_API_KEY=...,LLM_MODEL=gpt-4o(or Claude/Gemini).openai:LLM_PROVIDER=openai,OPENAI_API_KEY,OPENAI_MODEL.openai-compatible:LLM_PROVIDER=openai-compatible,LLM_BASE_URL,LLM_API_KEY,LLM_MODEL.objective_mapguard)./api/generatevia SSE (CORS preconfigured).Written for commit c307268. Summary will update on new commits.